// 游戏的核心GameScene类和GameLayer类，管理糖果的布局和消除逻辑;
function trace(){
    cc.log(Array.prototype.join.call(arguments, ", "));
}

// 在GameLayer中，有3层内容：
//      最底下的背景图、
//      中间10x 10 的糖果矩阵、
//      最上层的GameUI
var GameLayer = cc.Layer.extend({

    mapPanel:null,// 糖果矩阵、
    ui:null,      // 最上层的GameUI

    score:0, // 包括当前分数score、
    level:0, // 当前关卡level、
    steps:0, // 已用步数steps、
    limitStep:0,    //限制步数limitStep、
    targetScore:0,  // 目标分数targetScore
    map:null,

    /**
     * 糖果还在移动，不接受再次点击
     */
    moving:false,

    ctor: function () {
        this._super();

        var size = cc.winSize;

        //背景
        var bg = new cc.Sprite("res/bg.jpg");// 加载背景非常简单，只需要新建一个精灵Sprite即可，然后把背景放到游戏画面的中间位置。
        this.addChild(bg, 1);
        bg.x = size.width/2;
        bg.y = size.height/2;

        var clippingPanel = new cc.ClippingNode();// 我们需要考虑有一个遮罩，让超出10x10范围的新糖果不 显示。
        this.addChild(clippingPanel, 2);// 这里介绍一个新知识——遮罩节点（ClippingNode)。利用遮罩我们可以让指定范围内的内容显示，超出范围的内容隐藏。
        this.mapPanel = new cc.Layer();
        this.mapPanel.x = (size.width - Constant.CANDY_WIDTH*Constant.MAP_SIZE)/2;// 把ClippingNode放到屏 幕中间（ClippingNode的锚点跟Layer—样，跟Sprite不一样，所以调整到中间的坐标略有 不同）。
        this.mapPanel.y = (size.height - Constant.CANDY_WIDTH*Constant.MAP_SIZE)/2;
        clippingPanel.addChild(this.mapPanel, 1);

        var stencil = new cc.DrawNode();// 新建一个DrawNode，并利用drawRect方法绘制了一个正方形，该正方形正好 跟糖果矩阵640px x 640px区域重叠。
        stencil.drawRect(cc.p(this.mapPanel.x,this.mapPanel.y), cc.p(this.mapPanel.x+Constant.CANDY_WIDTH*Constant.MAP_SIZE,this.mapPanel.y+Constant.CANDY_WIDTH*Constant.MAP_SIZE),
            cc.color(0,0,0), 1, cc.color(0,0,0));
        clippingPanel.stencil = stencil;

        // 监听鼠标和触摸事件
        if("touches" in cc.sys.capabilities){
            cc.eventManager.addListener({
                event: cc.EventListener.TOUCH_ONE_BY_ONE,
                onTouchBegan: this._onTouchBegan.bind(this)
            }, this.mapPanel);
        } else {
            cc.eventManager.addListener({
                event: cc.EventListener.MOUSE,
                onMouseDown: this._onMouseDown.bind(this)
            }, this.mapPanel);
        }

        this._init();

        this.ui = new GameUI(this);
        this.addChild(this.ui, 3);

        return true;
    },

    // 初始化我们的糖果矩阵
    // 我们把游戏的核心数据和逻辑数据都初始化，
    // 再新建10x10 个随机糖果并添加到糖果矩阵层中。
    _init: function () {
        this.steps = 0;
        this.level = Storage.getCurrentLevel();
        this.score = Storage.getCurrentScore();
        this.limitStep = Constant.levels[this.level].limitStep;
        this.targetScore = Constant.levels[this.level].targetScore;

        // 为了在逻辑上方便管理所有糖果，我们上这里额外设计一个二维数组：this.map,通过列号和行号可以快速索引到某个糖果
        this.map = [];
        for (var i = 0; i < Constant.MAP_SIZE; i++) {
            var column = [];//列
            for (var j = 0; j < Constant.MAP_SIZE; j++) {
                var candy = Candy.createRandomType(i,j);
                this.mapPanel.addChild(candy);
                candy.x = i * Constant.CANDY_WIDTH + Constant.CANDY_WIDTH/2;
                candy.y = j * Constant.CANDY_WIDTH + Constant.CANDY_WIDTH/2;
                column.push(candy);
            }
            this.map.push(column);
        }
    },

    // 在 _onMouseDown 和 _onTouchBegan 中，通过鼠标坐标和糖果宽高计算出当前点击的糖果是哪一列哪一行。
    // 把画面坐标转化为逻辑的行列之后，我们就可以开始编写纯逻辑的代码了，其中_popCandy就是接下来逻辑的入口。
    _onTouchBegan: function (touch, event) {
        var column = Math.floor((touch.getLocation().x - this.mapPanel.x)/Constant.CANDY_WIDTH);// 与边界的距离/宽高 = 个数
        var row = Math.floor((touch.getLocation().y - this.mapPanel.y)/Constant.CANDY_WIDTH);
        this._popCandy(column, row);
        return true;
    },

    _onMouseDown: function (event) {
        var column = Math.floor((event.getLocationX() - this.mapPanel.x)/Constant.CANDY_WIDTH);
        var row = Math.floor((event.getLocationY() - this.mapPanel.y)/Constant.CANDY_WIDTH);
        this._popCandy(column, row);
    },

    // (1)	建立一个集合，存储全部相连的糖果。初始时只有被点击的糖果。
    // (2) 遍历集合中的糖果，判断该糖果的上、下、左、右4个方向的糖果是否跟该糖果 是同一颜色，如果是，则把旁边的糖果加到数组中。把新糖果加到集合中之前需要检查是否已经存在于集合中。
    // (3) 当遍历完集合的时候，相连的糖果就被找出来了。
    _popCandy: function (column, row) {
        if(this.moving)
            return;

        var joinCandys = [this.map[column][row]];
        var index = 0;
        var pushIntoCandys = function(element){
            if(joinCandys.indexOf(element) < 0)
                joinCandys.push(element);
        };
        while(index < joinCandys.length){
            var candy = joinCandys[index];
            if(this._checkCandyExist(candy.column-1, candy.row) && this.map[candy.column-1][candy.row].type == candy.type){
                pushIntoCandys(this.map[candy.column-1][candy.row]);
            }
            if(this._checkCandyExist(candy.column+1, candy.row) && this.map[candy.column+1][candy.row].type == candy.type){
                pushIntoCandys(this.map[candy.column+1][candy.row]);
            }
            if(this._checkCandyExist(candy.column, candy.row-1) && this.map[candy.column][candy.row-1].type == candy.type){
                pushIntoCandys(this.map[candy.column][candy.row-1]);
            }
            if(this._checkCandyExist(candy.column, candy.row+1) && this.map[candy.column][candy.row+1].type == candy.type){
                pushIntoCandys(this.map[candy.column][candy.row+1]);
            }
            index++;
        }

        // 得到相连糖果集合后，我们判断糖果个数，如果只有1个，那么就不执行消除的动作；
        // 如果大于等于2个，那么就删除这部分糖果，增加已用步数，并计算当前得到的分数。
        // 删除糖果后，再执行生成新糖果的逻辑，同时检查游戏的进度（胜利或失败）。
        // 另外，为了确保糖果补充过程中用户不能再次添加，我们再增加一个GameLayer属性moving。
        // 当moving 为 true 时，_popCandy不做处理；_popCandy开始执行后，设置 moving 为 true。
        if(joinCandys.length <= 1)
            return;

        this.steps++;
        this.moving = true;

        for (var i = 0; i < joinCandys.length; i++) {
            var candy = joinCandys[i];
            this.mapPanel.removeChild(candy);
            this.map[candy.column][candy.row] = null;
        }

        this.score += joinCandys.length*joinCandys.length;
        this._generateNewCandy();
        this._checkSucceedOrFail();
    },

    _checkCandyExist: function(i, j){
        if(i >= 0 && i < Constant.MAP_SIZE && j >= 0 && j < Constant.MAP_SIZE){
            return true;
        }
        return false;
    },
    // (1)	遍历糖果矩阵二维数组的每一列，每一列中再从下往上遍历该列的每个糖果。
    // (2)	每列遍历开始时，设置空位计数器为0。
    // (3) 如果某个位置为null,则表示该位置的糖果已在_popCandy中被删除，此时把该列的空位计数器加1,并在整列的最上方添加一个新随机糖果，并添加到二维数组的该列数据中。
    // (4) 如果某个位置为糖果，则需要安排这个糖果下落。当前空位计数器的数值正好就是该糖果需要下落的距离。设置糖果下落后，需要调整原位置为null,下落到的位置填入该糖果。
    // (5) 当遍历完该列最后一个糖果（包括刚新加的糖果）后，该列的检查工作就完成了。此时需要把超出10的数组位置删除。
    _generateNewCandy: function () {
        var maxTime = 0;
        for (var i = 0; i < Constant.MAP_SIZE; i++) {        //deal each column
            var missCount = 0;
            for (var j = 0; j < this.map[i].length; j++) {

                var candy = this.map[i][j];
                if(!candy){
                    var candy = Candy.createRandomType(i,Constant.MAP_SIZE+missCount);
                    this.mapPanel.addChild(candy);
                    candy.x = candy.column * Constant.CANDY_WIDTH + Constant.CANDY_WIDTH/2;
                    candy.y = candy.row * Constant.CANDY_WIDTH + Constant.CANDY_WIDTH/2;
                    this.map[i][candy.row] = candy;
                    missCount++;
                }else{
                    var fallLength = missCount;
                    if(fallLength > 0){
                        var duration = Math.sqrt(2*fallLength/Constant.FALL_ACCELERATION);// 下落的时间 下落的加速度FALL_ACCELERATION设置为30。
                        if(duration > maxTime)
                            maxTime = duration;
                        var move = cc.moveTo(duration, candy.x, candy.y-Constant.CANDY_WIDTH*fallLength).easing(cc.easeIn(2));    //easeIn参数是幂，以几次幂加速 //上述算法完成了糖果的补充工作，但是并没有下落的效果。所以我们让糖果位置变化 时执行一个模拟自由落体运动的动作：
                        candy.runAction(move);
                        candy.row -= fallLength;        //adjust all candy's row
                        this.map[i][j] = null;
                        this.map[i][candy.row] = candy;
                    }
                }
            }

            //移除超出地图的临时元素位置
            for (var j = this.map[i].length; j >= Constant.MAP_SIZE; j--) {
                this.map[i].splice(j, 1);
            }
        }
        this.scheduleOnce(this._finishCandyFalls.bind(this), maxTime);//当所有糖果的下落运动都完成后，我们把GameLayer的moving属性置为false,表示 运行下一轮的用户点击。为了实现这个功能，我们使用scheduleOnce和新建一个名为_finishCandyFalls的函数。
    },

    _finishCandyFalls: function () {
        this.moving = false;
    },

    _checkSucceedOrFail: function () {
        if(this.score > this.targetScore){
            this.ui.showSuccess();
            this.score += (this.limitStep - this.steps) * 30;
            Storage.setCurrentLevel(this.level+1);
            Storage.setCurrentScore(this.score);
            this.scheduleOnce(function(){
                cc.director.runScene(new GameScene());
            }, 3);
        }else if(this.steps >= this.limitStep){
            this.ui.showFail();
            Storage.setCurrentLevel(0);
            Storage.setCurrentScore(0);
            this.scheduleOnce(function(){
                cc.director.runScene(new GameScene());
            }, 3);
        }
    }

});

/**
 * GameScene，这是游戏的唯一一个场景
 * @type {Function}
 */
var GameScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        var layer = new GameLayer();
        this.addChild(layer);
    }
});